﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MT4Trainer
{
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.Diagnostics;
    using System.Reactive.Concurrency;
    using System.Reactive.Linq;
    using System.Reactive.Subjects;
    using System.Threading;
    using System.Windows.Forms.DataVisualization.Charting;

    using Encog.Engine.Network.Activation;
    using Encog.ML.Data;
    using Encog.ML.Data.Basic;
    using Encog.ML.Data.Temporal;
    using Encog.ML.Train;
    using Encog.ML.Train.Strategy;
    using Encog.Neural.Networks;
    using Encog.Neural.Networks.Layers;
    using Encog.Neural.Networks.Training;
    using Encog.Neural.Networks.Training.Anneal;
    using Encog.Neural.Networks.Training.Lma;
    using Encog.Neural.Networks.Training.Propagation.Back;
    using Encog.Neural.Networks.Training.Propagation.Resilient;
    using Encog.Util;
    using Encog.Util.Arrayutil;
    using Encog.Util.NetworkUtil;
    using Encog.Util.Simple;

    using MT4Trainer.Parallel_Helpers;

    using NLog.Config;
    using NLog.Targets;

    public partial class NetworkTrainer : Form
    {
        public Dictionary<int, double> DicChartPoints = new Dictionary<int, double>();

        private int currentIteration = 0;

        //Using this object to lock stuff...(like the charts).
        private object gate = new object();

        private NLog.Logger mLogger = NLog.LogManager.GetCurrentClassLogger();

        private int PrintWhenIterationIs = 50;

        public NetworkTrainer()
        {
            InitializeComponent();
            this.EvaluateEnd = Sunspots.Length - 1;
        }

        public NetworkTrainer(NLog.Logger logg)
        {
            InitializeComponent();
            this.mLogger = logg;
            this.EvaluateEnd = Sunspots.Length - 1;
        }

        public IMLDataSet EvaluationSet { get; set; }

        public BasicNetwork NetworkToTrain { get; set; }

        public ChartPoints NewPoints { get; set; }

        public IMLDataSet TrainingSet { get; set; }

        public void AddNewNLogTarget()
        {
            var richtgt = new RichTextBoxTarget
            {
                AutoScroll = true,
                ControlName = "RichAnalyst",
                FormName = "NetworkTrainer",
                UseDefaultRowColoringRules = true,
                Name = "RichAnalyst",
                ToolWindow = false
            };
            NLog.LogManager.Configuration.AddTarget("RichAnalyst", richtgt);
            NLog.LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", NLog.LogLevel.Warn, richtgt));
            NLog.LogManager.ReconfigExistingLoggers();
            mLogger = NLog.LogManager.GetCurrentClassLogger();
        }

        public void DoRefresh()
        {
            SafeInvokes.SafeInvoke(chart1, () => this.QuickFresh());
        }

        public void LongRunningOperationAsync(double ab, int iteration)
        {
            // UpdateCharts(ab, iteration);
            this.UpdateChartSafely(ab, iteration);
        }

        public void QuickFresh()
        {
            try
            {
                chart1.Refresh();
            }
            catch
            {
            }
        }

        public void Train(BasicNetwork network, IMLDataSet training)
        {
            //ITrain train = new ResilientPropagation(network, training);

            ITrain train = new Backpropagation(network, training);

            int epoch = 1;

            do
            {
                train.Iteration();
                this.UpdateChartSafely(train.Error, train.IterationNumber);
                //SafeInvokes.SafeInvoke(
                //            RichAnalyst, () => RichAnalyst.AppendText(@"Epoch #" + epoch + @" Error:" + train.Error));

                epoch++;
            }
            while (train.IterationNumber < 5000);
        }

        public double TrainNetworks(BasicNetwork network, IMLDataSet minis)
        {
            //  Backpropagation trainMain = new Backpropagation(network, minis, 0.0001, 0.6);

            this.currentIteration = 0;

            ResilientPropagation trainMain = new ResilientPropagation(network, minis);
            //set the number of threads below.
            ISubject<double> QuickSubject = new Subject<double>();

            QuickSubject.ObserveOn(chart1).Subscribe(delegate(double d) { this.ChartAction(d); });

            var timer = Observable.Interval(TimeSpan.FromMilliseconds(50));
            var idisp = timer.Subscribe(
                _ => DoRefresh(), () => Console.WriteLine("completed"));

            //DicChartPoints.ToObservable(Scheduler.TaskPool).Subscribe(
            //    this.DicoSubscribeOnNext, _ => mLogger.Error(_), () => mLogger.Info("Completed"));

            // train the neural network
            ICalculateScore score = new TrainingSetScore(minis);
            IMLTrain trainAlt = new NeuralSimulatedAnnealing(network, score, 10, 2, 100);
            // IMLTrain trainMain = new Backpropagation(network, minis, 0.0001, 0.01);

            Subject<string> quickWriter = new Subject<string>();
            //  quickWriter.SubscribeOn(Scheduler.CurrentThread).Subscribe(Console.WriteLine);

            quickWriter.SubscribeOn(RichAnalyst).Subscribe(Decoupled_Console.WriteLine);

            StopTrainingStrategy stop = new StopTrainingStrategy(0.0001, 200);

            var sw = new Stopwatch();
            sw.Start();

            while (!stop.ShouldStop() && trainMain.IterationNumber < Convert.ToInt32(this.MaxIterationTxtBox.Text))
            {
                trainMain.Iteration();
                Parallel.Invoke(delegate { QuickSubject.OnNext(trainMain.Error); });

                Parallel.Invoke(
                    () =>
                    quickWriter.OnNext(
                        (@"Iteration #:" + trainMain.IterationNumber + @" Error:" + trainMain.Error
                         + @" Genetic Iteration:" + trainAlt.IterationNumber)));

                //shc.Schedule(
                //    () =>
                //    mLogger.Info(
                //        (@"Iteration #:" + trainMain.IterationNumber + @" Error:" + trainMain.Error
                //         + @" Genetic Iteration:" + trainAlt.IterationNumber)));
                //         + 

                if (trainMain.Error < 0.0001) break;
            }
            sw.Stop();
            mLogger.Warn(@"Total elapsed time in seconds:" + TimeSpan.FromMilliseconds(sw.ElapsedMilliseconds).Seconds);

            idisp.Dispose();

            return trainMain.Error;
        }

        private void ChartAction(double error)
        {
            this.UpdateCharts(iterationNumber: currentIteration++, error: error);
        }

        private void DicoSubscribeOnNext(KeyValuePair<int, double> keyValuePair)
        {
            // this.UpdateChartSafely(keyValuePair.Value, keyValuePair.Key);
            // this.chart1.Series[0].Points.AddXY(keyValuePair.Key, keyValuePair.Value);

            this.UpdateCharts(iterationNumber: keyValuePair.Key, error: keyValuePair.Value);
        }

        private void GetEvaluationSetButton_Click(object sender, EventArgs e)
        {
            using (var open = new OpenFileDialog())
            {
                if (open.ShowDialog() == DialogResult.OK)
                {
                    mLogger.Info("Loading network from file :" + open.SafeFileName);
                    EvaluationSet = Encog.Util.NetworkUtil.NetworkUtility.LoadTraining(open.FileName);
                }
            }
            //We enable the training start button only if we have loaded a network and training...(Evaluation set is optional)
            if (NetworkToTrain != null && TrainingSet != null) this.StartTrainingButton.Enabled = true;
        }

        private void LoadNetworkButton_Click(object sender, EventArgs e)
        {
            using (var open = new OpenFileDialog())
            {
                if (open.ShowDialog() == DialogResult.OK)
                {
                    mLogger.Info("Loading network from file :" + open.SafeFileName);
                    NetworkToTrain = Encog.Util.NetworkUtil.NetworkUtility.LoadNetwork(open.FileName);
                }
            }
            //We enable the training start button only if we have loaded a network and training...(Evaluation set is optional)
            if (NetworkToTrain != null && TrainingSet != null) this.StartTrainingButton.Enabled = true;
        }

        private void LoadTrainingButton_Click(object sender, EventArgs e)
        {
            using (var open = new OpenFileDialog())
            {
                if (open.ShowDialog() == DialogResult.OK)
                {
                    mLogger.Info("Loading network from file :" + open.SafeFileName);
                    TrainingSet = Encog.Util.NetworkUtil.NetworkUtility.LoadTraining(open.FileName);
                }
            }
            //We enable the training start button only if we have loaded a network and training...(Evaluation set is optional)
            if (NetworkToTrain != null && TrainingSet != null) this.StartTrainingButton.Enabled = true;
        }

        private void NetworkTrainer_FormClosing(object sender, FormClosingEventArgs e)
        {
            NLog.LogManager.Configuration.RemoveTarget("RichAnalyst");
        }

        private void NetworkTrainer_Load(object sender, EventArgs e)
        {
            AddNewNLogTarget();
            NLogEncogLogger encLogger = new NLogEncogLogger(mLogger);
            // Encog.EncogFramework.Instance.RegisterPlugin(encLogger);
            mLogger.Info("Analyst Form started.");

            //Lets setup the charts..
            SetupCharts(chart1);
        }

        private void OnValuesTrainingUpdates(double d)
        {
            //mLogger.Trace("Update values from network : " + d + " For iteration : " + currentIteration);
            this.UpdateChartSafely(d, currentIteration++);
        }

        private void QuickTraining(BasicNetwork network, IMLDataSet training, int maxiterations)
        {
            ITrain train = new ResilientPropagation(network, training);

            int epoch = 1;

            do
            {
                train.Iteration();
                this.UpdateChartSafely(train.Error, train.IterationNumber);
                //Console.WriteLine(@"Epoch #" + epoch + @" Error:" + train.Error);
                epoch++;
            } while (train.IterationNumber < maxiterations);
        }

        private void QuickTrainingByDic(BasicNetwork network, IMLDataSet training, int maxiterations)
        {
            //var customers = new ObservableCollection<ChartPoints>();
            //ObservableErrors p = new ObservableErrors();

            // obDouble.Subscribe(x  () => chart1.Series[0].Points.AddY(x));

            int maxIt = Convert.ToInt32(MaxIterationTxtBox.Text);

            ITrain train = new ResilientPropagation(network, training);

            int epoch = 0;

            do
            {
                train.Iteration();

                //Lets print every 50 only.
                if (epoch % PrintWhenIterationIs == 0)
                    //ThreadPool.QueueUserWorkItem(
                    //    calls =>
                    //    SafeInvokes.SafeInvoke(
                    //        RichAnalyst,
                    //        () =>
                    //        RichAnalyst.AppendText((@"Iteration #:" + train.IterationNumber + @" Error:" + train.Error))));

                    SafeInvokes.SafeInvoke(chart1, () => this.LongRunningOperationAsync(train.Error, train.IterationNumber));

                epoch++;
            } while (train.IterationNumber < maxIt);

            RichAnalyst.AppendText("\n");
            RichAnalyst.AppendText("Done Training final error:" + train.Error + " For iteration's:" + train.IterationNumber);
        }

        private void RandomTrainingButton_Click(object sender, EventArgs e)
        {
            this.RandomTrainerMethod(200, 1);
        }

        private void resetToolStripMenuItem_Click(object sender, EventArgs e)
        {
            chart1.Series.Clear();
        }

        private void SaveNetworkToFile_Click(object sender, EventArgs e)
        {
            this.RichAnalyst.AppendText("Saving network to file...");
            using (var savediag = new SaveFileDialog())
            {
                if (savediag.ShowDialog() == DialogResult.OK)
                {
                    Encog.Util.NetworkUtil.NetworkUtility.SaveNetwork(savediag.FileName, this.NetworkToTrain);
                }
            }
        }

        private void SaveTrainingToFile_Click(object sender, EventArgs e)
        {
            this.RichAnalyst.AppendText("Saving network to file...");
            using (var savediag = new SaveFileDialog())
            {
                if (savediag.ShowDialog() == DialogResult.OK)
                {
                    Encog.Util.NetworkUtil.NetworkUtility.SaveTraining(savediag.FileName, this.TrainingSet);
                }
            }
        }

        private void SetupCharts(Chart chart)
        {
            chart.Series[0].ToolTip = "Data Point Y Value: #VALY{C0}";
        }

        private void StartTrainingButton_Click(object sender, EventArgs e)
        {
            Task.Factory.StartNew(
                () => Parallel.Invoke(() => this.TrainNetworks(this.NetworkToTrain, this.TrainingSet)));
        }

        private void StopPrintWhileTrainingButton_Click(object sender, EventArgs e)
        {
            PrintWhenIterationIs = 5000000;
        }

        private void SunSpotPredictionButton_Click(object sender, EventArgs e)
        {
            NormalizeSunspots(0.1, 0.9);
            BasicNetwork network = CreateNetwork();
            IMLDataSet training = GenerateTraining();

            // this.chart1.Series[0].Points.DataBindXY(DicChartPoints.Keys, DicChartPoints.Values);
            //this.chart1.Series[0].Points.Clear();
            //this.chart1.Series[1].Points.Clear();

            this.chart1.Series.Clear();
            if (chart1.Series == null || chart1.Series.Count == 0)
                chart1.Series.Add("Error Training");
            //    QuickTrainingByDic(network, training, 2000);

            Task.Factory.StartNew(() => Parallel.Invoke(() => TrainNetworks(network, training)));

            //  Train(network, training);
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            this.chart1.Invalidate();
        }

        private void timer1_Tick_1(object sender, EventArgs e)
        {
            //SafeInvokes.SafeInvoke(
            //         RichAnalyst, () => RichAnalyst.AppendText("Timer ticked"));
            //this.chart1.Invalidate();

            //Lets do it on the thread pool.

            ThreadPool.QueueUserWorkItem(call => SafeInvokes.SafeInvoke(chart1, () => chart1.Refresh()));
        }

        private double TrainKors(String what, BasicNetwork network, IMLDataSet trainingSet, string Method)
        {
            // train the neural network
            ICalculateScore score = new TrainingSetScore(trainingSet);
            IMLTrain trainAlt = new NeuralSimulatedAnnealing(network, score, 10, 2, 100);
            IMLTrain trainMain;
            if (Method.Equals("Leven"))
            {
                mLogger.Info("Using LevenbergMarquardtTraining");
                trainMain = new LevenbergMarquardtTraining(network, trainingSet);
            }
            else
                trainMain = new Backpropagation(network, trainingSet);

            var stop = new StopTrainingStrategy();
            trainMain.AddStrategy(new Greedy());
            trainMain.AddStrategy(new HybridStrategy(trainAlt));
            trainMain.AddStrategy(stop);

            int epoch = 0;
            while (!stop.ShouldStop())
            {
                trainMain.Iteration();
                mLogger.Info("Training " + what + ", Epoch #" + epoch + " Error:" + trainMain.Error);
                epoch++;
            }
            return trainMain.Error;
        }

        private void TrimChart()
        {
            lock (chart1)
            {
                double max = 0, min = 0;
               
                foreach (Series series in chart1.Series)
                {

                    foreach (var xpoints in series.Points)
                    {
                        foreach (double item in xpoints.YValues)
                        {
                            if (item > max) max = item;
                            if (min < item) min = item;
                        }
                    }
                    while (series.Points.Count > 30)
                    {
                        
                        series.Points.RemoveAt(0);
                        series.MarkerStep = 1;
                        series.XAxisType = AxisType.Secondary;
                        series.YAxisType = AxisType.Primary;
                       
                    }
                }

                this.chart1.ChartAreas[0].AxisY.Maximum = max;
                this.chart1.ChartAreas[0].AxisY.Minimum = 0;
                chart1.ChartAreas[0].AxisY.IntervalAutoMode=IntervalAutoMode.VariableCount;
             
                
            
                chart1.Update();
               
            }
        }

        private void UpdateCharts(double error, int iterationNumber)
        {
            lock (chart1)
            {
                SafeInvokes.SafeInvoke(chart1, () => chart1.Series.SuspendUpdates());
                SafeInvokes.SafeInvoke(chart1, () => chart1.Series[0].Points.AddXY(iterationNumber, error));

                //tk.Start(TaskScheduler.Default);

                // SafeInvokes.SafeInvoke(chart1, () => chart1.Series.ResumeUpdates());
                //  SafeInvokes.SafeInvoke(chart1, () => chart1.Refresh());
                //if (iterationNumber > 10)
                //{
                //    //double p = chart1.DataManipulator.Statistics.Mean(chart1.Series[0].Name);
                //    if (!chart1.Series.Contains(new Series("Mean"))) chart1.Series.Add(new Series("Mean"));
                //    if (chart1.Series[0].Points.Count > 20)
                //        SafeInvokes.SafeInvoke(chart1, () => chart1.Series["Mean"].Points.AddXY(iterationNumber, chart1.DataManipulator.Statistics.Mean(chart1.Series[0].Name)));
                //}

                TrimChart();

                //SafeInvokes.SafeInvoke(chart1, () => chart1.Series.ResumeUpdates());

                //if (iterationNumber % PrintWhenIterationIs == 0)
                //{
                //    //Lets remove the first 20 points...
                //    for (int[] i = { 0 }; i[0] < 20; i[0]++)
                //    {
                //        if (this.chart1.Series[0].Points.Count > i[0])
                //        {
                //            SafeInvokes.SafeInvoke(this.chart1, () => this.chart1.Series[0].Points.RemoveAt(i[0]));
                //        }
                //    }
                //    for (int[] i = { 0 }; i[0] < 10; i[0]++)
                //    {
                //        if (this.chart1.Series[1].Points.Count > i[0])
                //        {
                //            SafeInvokes.SafeInvoke(this.chart1, () => this.chart1.Series[1].Points.RemoveAt(i[0]));
                //        }
                //    }

                //    // SafeInvokes.SafeInvoke(chart1, () => chart1.Refresh());
                //}
                SafeInvokes.SafeInvoke(chart1, () => chart1.Series.ResumeUpdates());
            }
            //chart1.Series.SuspendUpdates();
            //chart1.Series[0].Points.AddXY(iterationNumber, error);
            //if (iterationNumber > 10)
            //{
            //    double p = chart1.DataManipulator.Statistics.Mean(chart1.Series[0].Name);

            //    chart1.Series["Mean"].Points.AddXY(iterationNumber, p);
            //}

            //SafeInvokes.SafeInvoke(chart1, () => chart1.Series.ResumeUpdates());

            //if (iterationNumber % PrintWhenIterationIs == 0)
            //{
            //    //Lets remove the first 20 points...
            //    for (int i = 0; i < 20; i++)
            //    {
            //        SafeInvokes.SafeInvoke(chart1, () => chart1.Series[0].Points.RemoveAt(i));
            //    }
            //    for (int i = 0; i < 10; i++)
            //    {
            //        SafeInvokes.SafeInvoke(chart1, () => chart1.Series[1].Points.RemoveAt(i));
            //    }

            //    SafeInvokes.SafeInvoke(chart1, () => chart1.Refresh());
            //}
            //  chart1.ChartAreas[0].AxisY.Interval = 0.0001;
            //   chart1.Series.Invalidate();
            //    chart1.Invalidate(true);

            //  chart1.Refresh();
            //if (this.chart1.InvokeRequired)
            //{
            //    chart1.Series[0].Points.AddXY(iterationNumber, error);
            //    this.chart1.Invalidate(true);
            //    this.chart1.Refresh();

            //}
            //else
            //{
            //    chart1.Series[0].Points.AddXY(iterationNumber, error);
            //    this.chart1.Invalidate(true);
            //    this.chart1.Refresh();
            //}
        }

        private void UpdateChartSafely(double error, int iteration)
        {
            // SafeInvokes.SafeInvoke(this.chart1, () => UpdateCharts(error, iteration));
            // ThreadPool.QueueUserWorkItem(call => UpdateCharts(error, iteration));

            chart1.Series[0].Points.AddXY(iteration, error);
        }

        #region random training

        /// <summary>
        /// Makes the inputs by randomizing with encog randomize , the normal random from net framework library.
        /// </summary>
        /// <param name="number">The number.</param>
        /// <returns></returns>
        public static double[] MakeInputs(int number)
        {
            Random rdn = new Random();
            Encog.MathUtil.Randomize.RangeRandomizer encogRnd = new Encog.MathUtil.Randomize.RangeRandomizer(-1, 1);
            double[] x = new double[number];
            for (int i = 0; i < number; i++)
            {
                x[i] = encogRnd.Randomize((rdn.NextDouble()));
            }
            return x;
        }

        /// <summary>
        /// Trains a random trainer.
        /// </summary>
        /// <param name="inputs">The inputs.</param>
        /// <param name="predictWindow">The predict window.</param>
        public double RandomTrainerMethod(int inputs, int predictWindow)
        {
            double[] firstinput = MakeInputs(inputs);
            double[] SecondInput = MakeInputs(inputs);
            double[] ThirdInputs = MakeInputs(inputs);
            double[] FourthInputs = MakeInputs(inputs);
            //double[] inp5 = MakeInputs(inputs);
            //double[] inp6 = MakeInputs(inputs);

            var pair = TrainerHelper.ProcessPairs(firstinput, firstinput, inputs, predictWindow);
            var pair2 = TrainerHelper.ProcessPairs(SecondInput, firstinput, inputs, predictWindow);
            var pair3 = TrainerHelper.ProcessPairs(ThirdInputs, firstinput, inputs, predictWindow);
            var pair4 = TrainerHelper.ProcessPairs(FourthInputs, firstinput, inputs, predictWindow);
            //var pair5 = TrainerHelper.ProcessPairs(inp5, firstinput, inputs, predictWindow);
            //var pair6 = TrainerHelper.ProcessPairs(inp6, firstinput, inputs, predictWindow);
            BasicMLDataSet SuperSet = new BasicMLDataSet();
            SuperSet.Add(pair);
            SuperSet.Add(pair2);

            SuperSet.Add(pair3);
            SuperSet.Add(pair4);
            var network = new BasicNetwork();
            network.AddLayer(new BasicLayer(null, true, SuperSet.InputSize));
            network.AddLayer(new BasicLayer(new ActivationTANH(), false, 20));
            network.AddLayer(new BasicLayer(new ActivationTANH(), true, 1));
            network.AddLayer(new BasicLayer(new ActivationLinear(), false, predictWindow));

            //var layer = new BasicLayer(new ActivationTANH(), true, SuperSet.InputSize);
            //layer.Network = network;

            network.Structure.FinalizeStructure();
            network.Reset();

            //QuickTraining(network, SuperSet, 1000);

            Task.Factory.StartNew(() => Parallel.Invoke(() => this.TrainNetworks(network, SuperSet)));

            return 0;
        }

        #endregion random training

        #region sunspots

        public const int EvaluateStart = 260;
        public const double MaxError = 0.01;
        public const int StartingYear = 1700;
        public const int TrainEnd = 259;
        public const int TrainStart = WindowSize;
        public const int WindowSize = 30;
        public int EvaluateEnd = 0;

        public double[] Sunspots = {
                                              0.0262, 0.0575, 0.0837, 0.1203, 0.1883, 0.3033,
                                              0.1517, 0.1046, 0.0523, 0.0418, 0.0157, 0.0000,
                                              0.0000, 0.0105, 0.0575, 0.1412, 0.2458, 0.3295,
                                              0.3138, 0.2040, 0.1464, 0.1360, 0.1151, 0.0575,
                                              0.1098, 0.2092, 0.4079, 0.6381, 0.5387, 0.3818,
                                              0.2458, 0.1831, 0.0575, 0.0262, 0.0837, 0.1778,
                                              0.3661, 0.4236, 0.5805, 0.5282, 0.3818, 0.2092,
                                              0.1046, 0.0837, 0.0262, 0.0575, 0.1151, 0.2092,
                                              0.3138, 0.4231, 0.4362, 0.2495, 0.2500, 0.1606,
                                              0.0638, 0.0502, 0.0534, 0.1700, 0.2489, 0.2824,
                                              0.3290, 0.4493, 0.3201, 0.2359, 0.1904, 0.1093,
                                              0.0596, 0.1977, 0.3651, 0.5549, 0.5272, 0.4268,
                                              0.3478, 0.1820, 0.1600, 0.0366, 0.1036, 0.4838,
                                              0.8075, 0.6585, 0.4435, 0.3562, 0.2014, 0.1192,
                                              0.0534, 0.1260, 0.4336, 0.6904, 0.6846, 0.6177,
                                              0.4702, 0.3483, 0.3138, 0.2453, 0.2144, 0.1114,
                                              0.0837, 0.0335, 0.0214, 0.0356, 0.0758, 0.1778,
                                              0.2354, 0.2254, 0.2484, 0.2207, 0.1470, 0.0528,
                                              0.0424, 0.0131, 0.0000, 0.0073, 0.0262, 0.0638,
                                              0.0727, 0.1851, 0.2395, 0.2150, 0.1574, 0.1250,
                                              0.0816, 0.0345, 0.0209, 0.0094, 0.0445, 0.0868,
                                              0.1898, 0.2594, 0.3358, 0.3504, 0.3708, 0.2500,
                                              0.1438, 0.0445, 0.0690, 0.2976, 0.6354, 0.7233,
                                              0.5397, 0.4482, 0.3379, 0.1919, 0.1266, 0.0560,
                                              0.0785, 0.2097, 0.3216, 0.5152, 0.6522, 0.5036,
                                              0.3483, 0.3373, 0.2829, 0.2040, 0.1077, 0.0350,
                                              0.0225, 0.1187, 0.2866, 0.4906, 0.5010, 0.4038,
                                              0.3091, 0.2301, 0.2458, 0.1595, 0.0853, 0.0382,
                                              0.1966, 0.3870, 0.7270, 0.5816, 0.5314, 0.3462,
                                              0.2338, 0.0889, 0.0591, 0.0649, 0.0178, 0.0314,
                                              0.1689, 0.2840, 0.3122, 0.3332, 0.3321, 0.2730,
                                              0.1328, 0.0685, 0.0356, 0.0330, 0.0371, 0.1862,
                                              0.3818, 0.4451, 0.4079, 0.3347, 0.2186, 0.1370,
                                              0.1396, 0.0633, 0.0497, 0.0141, 0.0262, 0.1276,
                                              0.2197, 0.3321, 0.2814, 0.3243, 0.2537, 0.2296,
                                              0.0973, 0.0298, 0.0188, 0.0073, 0.0502, 0.2479,
                                              0.2986, 0.5434, 0.4215, 0.3326, 0.1966, 0.1365,
                                              0.0743, 0.0303, 0.0873, 0.2317, 0.3342, 0.3609,
                                              0.4069, 0.3394, 0.1867, 0.1109, 0.0581, 0.0298,
                                              0.0455, 0.1888, 0.4168, 0.5983, 0.5732, 0.4644,
                                              0.3546, 0.2484, 0.1600, 0.0853, 0.0502, 0.1736,
                                              0.4843, 0.7929, 0.7128, 0.7045, 0.4388, 0.3630,
                                              0.1647, 0.0727, 0.0230, 0.1987, 0.7411, 0.9947,
                                              0.9665, 0.8316, 0.5873, 0.2819, 0.1961, 0.1459,
                                              0.0534, 0.0790, 0.2458, 0.4906, 0.5539, 0.5518,
                                              0.5465, 0.3483, 0.3603, 0.1987, 0.1804, 0.0811,
                                              0.0659, 0.1428, 0.4838, 0.8127
                                          };

        private double[] _closedLoopSunspots;
        private double[] _normalizedSunspots;

        public BasicNetwork CreateNetwork()
        {
            var network = new BasicNetwork();
            network.AddLayer(new BasicLayer(WindowSize));
            network.AddLayer(new BasicLayer(10));
            network.AddLayer(new BasicLayer(1));
            network.Structure.FinalizeStructure();
            network.Reset();
            return network;
        }

        public IMLDataSet GenerateTraining()
        {
            var result = new TemporalMLDataSet(WindowSize, 1);

            var desc = new TemporalDataDescription(
                TemporalDataDescription.Type.Raw, true, true);
            result.AddDescription(desc);

            for (int year = TrainStart; year < TrainEnd; year++)
            {
                var point = new TemporalPoint(1) { Sequence = year };
                point.Data[0] = _normalizedSunspots[year];
                result.Points.Add(point);
            }

            result.Generate();

            return result;
        }

        public void NormalizeSunspots(double lo, double hi)
        {
            var norm = new NormalizeArray { NormalizedHigh = hi, NormalizedLow = lo };

            // create arrays to hold the normalized sunspots
            _normalizedSunspots = norm.Process(Sunspots);
            _closedLoopSunspots = EngineArray.ArrayCopy(_normalizedSunspots);
        }

        #endregion sunspots

        #region chart points.

        public class ChartPoints
        {
            public double CurrentError { get; set; }

            public int Iteration { get; set; }

            public DateTimeOffset OrderDate { get; set; }
        }

        public class ObservableErrors
        {
            public ObservableErrors() { Orders = new ObservableCollection<ChartPoints>(); }

            public string CustomerName { get; set; }

            public ObservableCollection<ChartPoints> Orders { get; private set; }

            public string Region { get; set; }
        }

        #endregion chart points.
    }
}